package com.yifeng.repo.micro.service.server.engine.configure;

import com.gomcarter.frameworks.base.common.AssertUtils;
import com.gomcarter.frameworks.base.config.BaseConfProperties;
import com.yifeng.repo.base.params.BizCacheParam;
import com.yifeng.repo.base.security.auth.constant.SecurityTypeEnum;
import com.yifeng.repo.base.security.context.filter.AppSecurityFilter;
import com.yifeng.repo.base.security.context.listener.AppContextListener;
import com.yifeng.repo.base.security.context.params.SecurityCheckParam;
import com.yifeng.repo.base.utils.action.BizCacheAction;
import com.yifeng.repo.base.utils.action.impl.BizCompositeCacheAction;
import com.yifeng.repo.base.utils.redis.worker.RedisCacheWorker;
import com.yifeng.repo.micro.service.server.engine.request.AppLifeCycleListener;
import com.yifeng.repo.micro.service.server.engine.request.openx.JsonHandlerMethodArgumentResolver;
import com.yifeng.repo.micro.service.server.engine.request.openx.JsonRequestFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.ServletRequestListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Created by daibing on 2023/3/15.
 */
@Slf4j
@Configuration
@EnableConfigurationProperties(ServerEngineRequestProperties.class)
@ConditionalOnProperty(prefix = ServerEngineRequestProperties.PREFIX, name = "enabled", matchIfMissing = true)
public class ServerEngineRequestConfiguration {

    @Value("${spring.application.name}")
    private String applicationName;

    @Resource
    private ServerEngineRequestProperties properties;

    @Resource
    private ServerEngineMasterProperties masterProperties;

    @Resource
    private BaseConfProperties baseConfProperties;

    @Resource
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @PostConstruct
    public void init() {
        this.checkAndUpdateProperties();
        this.addArgumentResolvers();
    }

    @Bean
    public AppLifeCycleListener appLifeCycleListener(@Autowired(required = false) ForceTransactionProperties forceTransactionProperties) {
        Boolean isPrintLog4ForceTransaction = Optional.ofNullable(forceTransactionProperties)
                .map(ForceTransactionProperties::isPrintLog)
                .orElse(false);
        return new AppLifeCycleListener(applicationName, isPrintLog4ForceTransaction);
    }

    @Bean
    public ServletListenerRegistrationBean<ServletRequestListener> appContextListener() {
        ServletListenerRegistrationBean<ServletRequestListener> registration = new ServletListenerRegistrationBean<>();
        registration.setListener(
                new AppContextListener(properties.getCopyHeaderPrefixes(),
                        baseConfProperties.getPropertyTransformerUriPatternList(),
                        properties.isEnablePrintRequestCostTime()));
        return registration;
    }

    @Bean
    @ConditionalOnProperty(prefix = ServerEngineRequestProperties.PREFIX, name = "enable-security")
    public FilterRegistrationBean<Filter> appSecurityFilter(@Autowired(required = false) RedisCacheWorker redisCacheWorker) {
        BizCacheParam bizCacheParam = Optional.ofNullable(masterProperties.getBizCacheParam()).orElse(new BizCacheParam(applicationName));
        List<SecurityCheckParam> securityCheckParams = buildSecurityCheckParams();
        BizCacheAction bizCacheAction = new BizCompositeCacheAction(bizCacheParam, redisCacheWorker);
        AppSecurityFilter appSecurityFilter = new AppSecurityFilter(
                properties.getSecurityExcludeUris(), securityCheckParams,
                masterProperties.getAccountConsoleParam(), masterProperties.getCustomerConsoleParam(), masterProperties.getSupplyConsoleParam(),
                bizCacheAction);
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
        registration.setFilter(appSecurityFilter);
        registration.addUrlPatterns(properties.getSecurityUrlPatterns().toArray(new String[0]));
        registration.setName(AppSecurityFilter.class.getSimpleName());
        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 1);
        return registration;
    }

    @Bean
    @ConditionalOnProperty(prefix = ServerEngineRequestProperties.PREFIX, name = "enable-json-request")
    public FilterRegistrationBean<Filter> jsonRequestFilter() {
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
        registration.setDispatcherTypes(DispatcherType.REQUEST);
        registration.setFilter(new JsonRequestFilter(properties.getJsonRequestExcludeUris()));
        registration.addUrlPatterns(properties.getJsonRequestUrlPatterns().toArray(new String[0]));
        registration.setName(JsonRequestFilter.class.getSimpleName());
        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 2);
        return registration;
    }

    private void checkAndUpdateProperties() {
        // 如果未配置任何鉴权参数，就将默认2B兼容参数拷贝过来
        if (properties.getAccountSecurityCheckParam() == null
                && properties.getCustomerSecurityCheckParam() == null
                && properties.getSupplySecurityCheckParam() == null) {
            properties.setAccountSecurityCheckParam(buildBusinessSecurityCheckParam());
        }
    }

    private SecurityCheckParam buildBusinessSecurityCheckParam() {
        SecurityCheckParam businessCheckParam = new SecurityCheckParam();
        businessCheckParam.setSecurityType(SecurityTypeEnum.BUSINESS);
        businessCheckParam.setAllowPassByNonToken(properties.isAllowPassByNonToken());
        businessCheckParam.setCheckApi(properties.isSecurityCheckApiPermission());
        businessCheckParam.setLoadPermission(properties.isSecurityLoadDataPermission());
        businessCheckParam.setSuccessIfCheckSecuredException(properties.isSuccessIfCheckSecuredException());
        return businessCheckParam;
    }

    private List<SecurityCheckParam> buildSecurityCheckParams() {
        List<SecurityCheckParam> securityCheckParams = new ArrayList<>(3);
        if (properties.getAccountSecurityCheckParam() != null) {
            securityCheckParams.add(properties.getAccountSecurityCheckParam());
        }
        if (properties.getCustomerSecurityCheckParam() != null) {
            securityCheckParams.add(properties.getCustomerSecurityCheckParam());
        }
        if (properties.getSupplySecurityCheckParam() != null) {
            securityCheckParams.add(properties.getSupplySecurityCheckParam());
        }
        return securityCheckParams;
    }

    private void addArgumentResolvers() {
        // 未启用JsonRequest
        if (!properties.isEnableJsonRequest()) {
            return;
        }

        // 2. 获取框架定义的参数解析集合(不可变集合)，并且找出需要的分解器
        List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
        AssertUtils.notNull(argumentResolvers, "argumentResolverList must not be null!");

        // 3. 将自定义Json分解器插入第一个，并且重新设置处理器
        List<HandlerMethodArgumentResolver> engineArgumentResolvers = new ArrayList<>(argumentResolvers.size() + 1);
        engineArgumentResolvers.add(new JsonHandlerMethodArgumentResolver(argumentResolvers));
        engineArgumentResolvers.addAll(argumentResolvers);
        requestMappingHandlerAdapter.setArgumentResolvers(engineArgumentResolvers);
    }

}